package com.xnx3.doc;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.xnx3.FileUtil;
import com.xnx3.ScanClassUtil;
import com.xnx3.StringUtil;
import com.xnx3.SystemUtil;
import com.xnx3.UrlUtil;
import com.xnx3.doc.bean.ClassBean;
import com.xnx3.doc.bean.MethodBean;
import com.xnx3.doc.bean.ParamBean;
import com.xnx3.doc.javadoc.JavaDocBean;
import com.xnx3.doc.javadoc.JavaDocMethodBean;
import com.xnx3.doc.javadoc.JavaDocUtil;
import com.xnx3.net.HttpResponse;
import com.xnx3.net.HttpUtil;
import net.sf.json.JSONArray;
import net.sf.json.JSONObject;

/**
 * JavaDoc 生成接口文档
 * @author 管雷鸣
 *
 */
public class JavaDoc {
	private String packageName;	//要搜索的包名，如 com.xnx3.wangmarket ，会自动搜索这个包下所有符合的自动生成文档
	public String templatePath = "http://res.zvo.cn.obs.cn-north-4.myhuaweicloud.com/javadoc/v1.12/";	//模板所在路径，如 http://res.zvo.cn/javadoc/template/ 这个目录下有 template.html、style.css、javadoc.js
	
	/******* 生成的文档中的一些默认值 *******/
	public String name = "API文档";		//文档名字，如 云商城用户端API文档，不设置默认为 API文档
	public String version = "1.0"; //当前文档对应你系统的版本，默认是1.0，这里是你当前系统的版本
	public String domain = "http://localhost:8080";		//API接口请求域名，不设置默认是 http://localhost:8080，这里是第一次打开文档，没有设置请求域名时，默认的域名
	public String token = "";		//第一次打开文档时，默认的token值，不设置默认为 “” 空字符串
	public String welcome = "";		//会在index.html中显示，作为入口欢迎页的说明显示。可以将一些文档通用性说明放到这里。可设置为html格式（CSS直接写到里面）以使之更美观。
	
	/**
	 * java源文件所在的文件夹路径，里面的路径如：
	 * H:\git\wm\
	 */
	public static List<String> javaSourceFolderList = new ArrayList<String>();
	
	/**
	 * 创建 JavaDoc 接口文档对象
	 * @param packageName 要搜索的包名，如 com.xnx3.wangmarket ，会自动搜索这个包下所有符合的自动生成文档
	 */
	public JavaDoc(String packageName) {
		this.packageName = packageName;
		
		String[] subProjectName = {"wm","xnx3_util","xnx3_weixin","wangmarket_shop","wangmarket","page.java"};
		
		File file = new File(SystemUtil.getCurrentDir());
		for (int i = 0; i < subProjectName.length; i++) {
			String subJarProjectPath = file.getParentFile().getAbsolutePath()+File.separator+subProjectName[i]+File.separator;
			JavaDoc.javaSourceFolderList.add(subJarProjectPath);
		}
	}
	
	
	public static void main(String[] args) {
		JavaDoc doc = new JavaDoc("com.xnx3.demo");
		doc.javaSourceFolderList.add("elseProject");
		doc.generateHtmlDoc();
	}
	
	/**
	 * 生成 HTML DOC 文档
	 */
	public void generateHtmlDoc() {
		List<ClassBean> list = searchController();
		
		//过滤一些如request、model 这种的无用的参数
		for (int i = 0; i < list.size(); i++) {
			ClassBean classBean = list.get(i);
			for(int m = 0; m < classBean.getMethodList().size(); m++) {
				MethodBean mb = classBean.getMethodList().get(m);
					
				List<String> removeKey = new ArrayList<String>();
				for (Map.Entry<String, ParamBean> entry : mb.getParams().entrySet()) {
					if("HttpServletRequest".equalsIgnoreCase(entry.getValue().getType())) {
						removeKey.add(entry.getKey());
						continue;
					}
					if("Model".equalsIgnoreCase(entry.getValue().getType())) {
						removeKey.add(entry.getKey());
						continue;
					}
					if("HttpServletResponse".equalsIgnoreCase(entry.getValue().getType())) {
						removeKey.add(entry.getKey());
						continue;
					}
					
					if("MultipartFile".equalsIgnoreCase(entry.getValue().getType())) {
						entry.getValue().setType("文件");
						entry.getValue().setDefaultValue("请选择一个文件");
						continue;
					}
				}
				for (int rk = 0; rk < removeKey.size(); rk++) {
					mb.getParams().remove(removeKey.get(rk));
				}
			}
		}
		
		/**** 拉下最新的模板、css、js相关 ****/
		TemplateUtil template = new TemplateUtil(this);
		String apiTemplate = template.getTemplate();
		
		
		//创建存放 html的目录
		String path = SystemUtil.getCurrentDir();
		String htmldocPath = path+"/htmldoc/";
		File file = new File(htmldocPath);
		file.mkdir();
		Log.log("自动创建doc文档存放目录： "+htmldocPath);
		
		/**** 生成具体api的文档 ****/
		for (int i = 0; i < list.size(); i++) {
			ClassBean classBean = list.get(i);
			List<MethodBean> methodList = classBean.getMethodList();
			for(int m = 0; m<methodList.size(); m++) {
				MethodBean methodBean = methodList.get(m);
				//System.out.println(methodBean.getParams());
				String html = apiTemplate;
				html = TemplateUtil.replaceAll(html, "\\{api.url\\}", methodBean.getApiUrl());
				html = TemplateUtil.replaceAll(html, "\\{api.commentText\\}", methodBean.getCommentText());
				html = TemplateUtil.replaceAll(html, "\\{api.author\\}", methodBean.getAuthor());
				html = TemplateUtil.replaceAll(html, "\\{api.params\\}", JSONObject.fromObject(methodBean.getParams()).toString());
				html = TemplateUtil.replaceAll(html, "\\{api.returnCommentText\\}", methodBean.getReturnCommentText());
				html = TemplateUtil.replaceAll(html, "\\{api.return\\}", JSONObject.fromObject(methodBean.getReturnValue()).toString());
				html = TemplateUtil.replaceAll(html, "\\{api.method\\}", methodBean.getMethodType());
				html = TemplateUtil.replaceAll(html, "\\{api.login\\}", methodBean.isLogin()+"");
				FileUtil.write(htmldocPath+replaceHtmlFileName(methodBean.getApiUrl())+".html", html);
				Log.info("生成文件 > "+methodBean.getApiUrl()+".html");
			}
		}
		
		//生成 style.css
//		FileUtil.write(htmldocPath+"style.css", cssHr.getContent());
		//生成 htmldoc.js
//		FileUtil.write(htmldocPath+"htmldoc.js", jsHr.getContent());
		
		/**** 生成文档目录 ****/
		String indexTemplate = template.getIndex();
		List<Map<String, Object>> outlineList = new ArrayList<Map<String, Object>>();
		for (int i = 0; i < list.size(); i++) {
			Map<String, Object> mm = new HashMap<String, Object>();
			List<Map<String, String>> methodMapList = new ArrayList<Map<String,String>>();
			
			ClassBean classBean = list.get(i);
			List<MethodBean> methodList = classBean.getMethodList();
			for(int m = 0; m<methodList.size(); m++) {
				MethodBean methodBean = methodList.get(m);
				Map<String, String> map = new HashMap<String, String>();
				map.put("urlFile", methodBean.getUrlFile());
				map.put("getApiUrl", methodBean.getApiUrl());
				map.put("commentText", getFirstLine(methodBean.getCommentText()));
				map.put("methodName", methodBean.getMethodName());
				map.put("html", replaceHtmlFileName(methodBean.getApiUrl())+".html");
//				methodMap.put(classBean.getClassName()+"."+methodBean.getMethodName()+".html", map);
				methodMapList.add(map);
			}
			
			//首页因为要只显示第一行备注，所以进行换行判断
			mm.put("apiList", methodMapList);
			mm.put("commentText", getFirstLine(classBean.getCommentText()));
			mm.put("author", classBean.getAuthor());
			mm.put("urlPath", classBean.getUrlPath());
			outlineList.add(mm);
		}
		indexTemplate = TemplateUtil.replaceAll(indexTemplate, "\\{welcome\\}", this.welcome);
		FileUtil.write(htmldocPath+"index.html", indexTemplate);
		Log.info("生成目录入口文件 > index.html");
		
		String javadocJs = TemplateUtil.replaceAll(template.getJavaDocJs(), "\\{outline\\}", JSONArray.fromObject(outlineList).toString());
		FileUtil.write(htmldocPath+"javadoc.js", javadocJs);
		Log.info("生成javadoc.js文件 > javadoc.js");
		
		//生成 style.css
		FileUtil.write(htmldocPath+"style.css", template.getStyleCss());
		Log.info("生成javadoc.js文件 > style.css");
		
		//打开htmldoc文件夹
		Log.info("正在打开文件夹...");
		SystemUtil.openLocalFolder(htmldocPath);
	}
	
	/**
	 * 多行中取第一行
	 * @param commentText
	 * @return
	 */
	private static String getFirstLine(String commentText) {
		if(commentText == null) {
			return "";
		}
		commentText = commentText.trim().split("\r|\n")[0];
		commentText = commentText.trim().split("<br|<p>")[0];
		return commentText;
	}

	
	/**
	 * 搜索class文件，整理出类、方法、参数的大结构（尚无javadoc注释）
	 * @return
	 */
	public List<ClassBean> searchController() {
		List<ClassBean> classBeanList = new ArrayList<ClassBean>();
		
		List<Class<?>> classList = ScanClassUtil.getClassSearchAnnotationsName(ScanClassUtil.getClasses(this.packageName), "RequestMapping");
		for (Class<?> controller : classList) {
        	ClassBean classBean = new ClassBean(); 
        	
        	//找到插件注册类了，进行注册插件
        	if(controller.getAnnotation(RequestMapping.class) != null){
    			//ConsoleUtil.info(controller.getName());
    			RequestMapping requestMapping_controller = (RequestMapping) controller.getAnnotation(RequestMapping.class);
//    			
    			String javaFilePath = JavaDocUtil.getJavaAbsolutePath(controller);
    			if(javaFilePath == null) {
    				//文件不存在
    				//ConsoleUtil.log("文件不存在 - "+javaFilePath);
    				continue;
    			}
    			
    			
    			JavaDocBean javaDocBean = JavaDocUtil.getJavaDoc(UrlUtil.getPath(javaFilePath), UrlUtil.getFileName(javaFilePath));
    			//Log.log("---"+JSONObject.fromObject(javaDocBean).toString());
    			classBean.setClassName(javaDocBean.getClassName());
    			classBean.setCommentText(javaDocBean.getCommentText());
    			if(classBean.getCommentText().indexOf(Tag.IGNORE) > -1) {
    				//忽略这个类
    				continue;
    			}
    			
    			
    			Map<String, JavaDocMethodBean> javaDocMethodMap = new HashMap<String, JavaDocMethodBean>();
    			List<JavaDocMethodBean> javaDocMethodBean = javaDocBean.getMethodList();
    			for (int i = 0; i < javaDocMethodBean.size(); i++) {
    				javaDocMethodMap.put(javaDocMethodBean.get(i).getMethodName(), javaDocMethodBean.get(i));
//    				System.out.println("\t"+javaDocMethodBean.get(i).getMethodName());
				}
    			
    			
    			if(requestMapping_controller.value().length < 1) {
    				Log.error(requestMapping_controller+", url path is null");
    				continue;
    			}
    			//ConsoleUtil.log("-----"+requestMapping_controller.value()[0]+"");
    			classBean.setUrlPath(requestMapping_controller.value()[0]);
    			
//    			Method[] methods = controller.getMethods();
    			Method[] methods = controller.getDeclaredMethods();
    			for (int i = 0; i < methods.length; i++) {
    				MethodBean methodBean = new MethodBean();
    				
    				Method method = methods[i];
    				RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
    				PostMapping postMapping = method.getAnnotation(PostMapping.class);
    				if(requestMapping == null && postMapping == null) {
						continue;
					}
    				if(method.getName().indexOf("$") > -1) {
            			continue;
            		}
    				
    				//判断是get请求还是post请求还是什么其他请求
    				if(requestMapping != null) {
    					if(requestMapping.method().length > 0) {
    						methodBean.setMethodType(requestMapping.method()[0].name());
    					}
    				}else if(postMapping != null) {
    					methodBean.setMethodType("POST");
    				}
    				
    				//判断方法是否有  @ResponseBody 标注
//    				ResponseBody responseBody = method.getAnnotation(ResponseBody.class);
//    				if(responseBody == null) {
//    					//没有这个，那就是不返回json格式，忽略这个
//    					continue;
//    				}
    				
//					System.out.println("\t"+methods[i].getName()+"\t"+methods[i].getReturnType().getName());
					methodBean.setMethodName(methods[i].getName());
					methodBean.setReturnVoClassName(methods[i].getReturnType().getName());
					methodBean.setUrlFile(requestMapping.value()[0]);
					methodBean.setApiUrl(classBean.getUrlPath()+methodBean.getUrlFile());
					methodBean.setReturnValue(JavaDocUtil.getBeanDoc(methodBean.getReturnVoClassName()));
					
					JavaDocMethodBean javadocMethodBean = javaDocMethodMap.get(methodBean.getMethodName());
					if(javaDocMethodBean != null) {
						methodBean.setCommentText(javadocMethodBean.getCommentText());
						methodBean.setAuthor(javadocMethodBean.getAuthor());
						methodBean.setReturnCommentText(javadocMethodBean.getReturnCommentText());
					}
					
					//生成接口是否包含这个方法，是否要忽略
					if(methodBean.getCommentText().indexOf(Tag.IGNORE) > -1) {
	    				//忽略这个类
	    				continue;
	    			}
					
//					System.out.println("\t"+methodBean.getMethodName()+", "+requestMapping.method());
					Parameter[] ts = method.getParameters();
					for (int j = 0; j < ts.length; j++) {
						ParamBean paramBean = new ParamBean();
						paramBean.setName(ts[j].getName());
						paramBean.setType(ts[j].getType().getSimpleName());
//						System.out.println(ts[j].getName());
						if(ts[j].getAnnotations().length > 0) {
//							System.out.println("\t"+ts[j].getAnnotations()[0]);
						}
						
						//先找doc注释
						if(javadocMethodBean.getParams() != null) {
							com.xnx3.doc.javadoc.ParamBean javadocParamBean = javadocMethodBean.getParams().get(paramBean.getName());
							if(javadocParamBean != null) {
								paramBean.setCommentText(javadocParamBean.getCommentText());
							}
						}
						
						//再找 springmvc 的 @requestparam。  如果 doc注释跟这个冲突，以这个为准，注释的优先低
						RequestParam rp = ts[j].getAnnotation(RequestParam.class);
						if(rp != null) {
//							System.out.println(rp.required());
							if(rp.defaultValue() != null && rp.defaultValue().length() > 0) {
								// 如果springmvc的 defaultValue不设置会出现乱码，莫名其妙，所以增加了几个replace
								String defaultValue = rp.defaultValue().trim().replaceAll("\r|\n", javaFilePath).trim().replaceAll("", "").replaceAll("", "").replaceAll("", "").replaceAll(" ", "").replaceAll(" ", "");
								if(defaultValue.length() > 0) {
									paramBean.setDefaultValue(defaultValue);
								}
								
//								System.out.println(StringUtil.removeBlank(paramBean.getDefaultValue())+"  -"+paramBean.getName());
							}
							paramBean.setRequired(rp.required());
						}
						
						
						methodBean.getParams().put(paramBean.getName(), paramBean);
					}
					//判断哪些是 doc文档中（javadocMethodBean.getParams()） 有的，但是在 springmvc传参 （method.getParameters()）中没有的
					for (Entry<String, com.xnx3.doc.javadoc.ParamBean> docEntry : javadocMethodBean.getParams().entrySet()) {
						
						//继续遍历 springmvc的 @requestParam
						boolean find = false;	//是否发现相同的了
						for (Entry<String, ParamBean> requestParamMap : methodBean.getParams().entrySet()) {
							if(requestParamMap.getKey().equals(docEntry.getKey())) {
								//发现相同，判断下一个
								find = true;
								break;
							}
						}
						if(find) {
							continue;
						}
						
						//没有发现相同，发现javadoc有，但是springmvc的 @requestParam 么有，那也加入进参数文档
						ParamBean pb = new ParamBean();
						pb.setName(docEntry.getValue().getName());
						pb.setCommentText(docEntry.getValue().getCommentText());
						methodBean.getParams().put(pb.getName(), pb);
					}
					
					
					classBean.getMethodList().add(methodBean);
				}
        	}
        	
        	classBeanList.add(classBean);
        }
        return classBeanList;
	}
	
	
	public static boolean haveResponseBody(Annotation[] annotations) {
		for (int j = 0; j < annotations.length; j++) {
//			PluginRegister plugin = (PluginRegister) c.getAnnotation(PluginRegister.class);
			Class<? extends Annotation> annotation = annotations[j].annotationType(); 
//			System.out.println(annotations[j]);
//			System.out.println(annotation.get);
		}
		
		return false;
	}
	
	/**
	 * 获取某个类及其所有父类的所有 field （private、provate、public的都会获取）
	 * @param cls
	 * @return
	 */
	public static Field[] getAllFields(Class cls){
		List<Field> fieldList = new ArrayList<>();
		while (cls != null){
			
			fieldList.addAll(new ArrayList<>(Arrays.asList(cls.getDeclaredFields())));
			cls = cls.getSuperclass();
		}
		Field[] fields = new Field[fieldList.size()];
		fieldList.toArray(fields);
		return fields;
	}
	
	/**
	 * 将api url中的\ /替换为. 免得创建不了文件
	 * @param name 传入如 /app/user/updateHead.json
	 * @return 返回如 app.user.updateHead.json
	 */
	public static String replaceHtmlFileName(String apiUrl) {
		String name = apiUrl.replaceAll("/", ".").replaceAll("\\\\", ".");
		if(name.indexOf(".") == 0) {
			name = name.substring(1, name.length());
		}
		return name;
	}
}
